package com.yeskery.nut.http.controller;

import com.yeskery.nut.core.*;
import com.yeskery.nut.util.ClassLoaderUtils;
import com.yeskery.nut.util.StringUtils;

import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
import java.util.stream.Collectors;

/**
 * 默认的静态资源控制器
 * @author sprout
 * 2022-05-12 11:40
 */
public class DefaultStaticResourceController implements StaticResourceController {

    /** 类路径 */
    private static final String CLASS_PATH_RESOURCE = "classpath:";

    /** 绝对类资源前缀 */
    private static final String ABS_CLASS_RESOURCE_PREFIX = "/";

    /** 默认间距 */
    private static final int DEFAULT_SPACING = 60;

    /** 预览开始html */
    private static final String PREVIEW_START_HTML1;

    /** 预览开始html */
    private static final String PREVIEW_START_HTML2;

    /** 预览开始html */
    private static final String PREVIEW_START_HTML3;

    /** 预览上一页html */
    private static final String PREVIEW_PREVIOUS_HTML;

    /** 预览结束html */
    private static final String PREVIEW_END_HTML;

    /** 静态资源路径数组 */
    private String[] staticResourceDirectories;

    /** 静态资源路径 */
    private Path path;

    /** 目录预览状态 */
    private boolean directoryPreviewStatus;

    static {
        PREVIEW_START_HTML1 = "<html>\n<head><title>Index of ";
        PREVIEW_START_HTML2 = " - Sprout Nut</title></head>\n<body bgcolor=\"white\">\n<h1>Index of ";
        PREVIEW_START_HTML3 = "</h1><hr><pre>\n";
        PREVIEW_PREVIOUS_HTML = "<a href=\"../\">../</a>";
        PREVIEW_END_HTML = "</pre><hr><h6>Power By Sprout Nut. Version: " + Version.VERSION + "</h6></body>\n</html>";
    }

    /**
     * 构建一个默认的静态资源控制器
     *
     * @param staticResourceDirectories 静态资源路径数组
     */
    public DefaultStaticResourceController(String[] staticResourceDirectories) {
        this.staticResourceDirectories = staticResourceDirectories;
    }

    @Override
    public String[] getStaticResourceDirectories() {
        return staticResourceDirectories;
    }

    @Override
    public void setStaticResourceDirectories(String[] dirs) {
        this.staticResourceDirectories = dirs;
    }

    @Override
    public Path getStaticResourceRequestPath() {
        return path;
    }

    @Override
    public void setStaticResourceRequestPath(Path path) {
        this.path = path;
    }

    @Override
    public void setDirectoryPreviewStatus(boolean directoryPreviewStatus) {
        this.directoryPreviewStatus = directoryPreviewStatus;
    }

    @Override
    public void doGet(Request request, Response response, Execution execution) {
        String path = request.getPath();
        if (!this.path.match(path)) {
            throw new ResponseNutException(ResponseCode.FORBIDDEN);
        }
        String relativeRequestPath = getRelativeRequestPath(request, getStaticResourceRequestPath());
        for (String directory : staticResourceDirectories) {
            if (directory.startsWith(CLASS_PATH_RESOURCE)) {
                String sourceFolder = directory.substring(CLASS_PATH_RESOURCE.length());
                String sourceName = sourceFolder + relativeRequestPath;
                while (sourceName.startsWith(ABS_CLASS_RESOURCE_PREFIX)) {
                    sourceName = sourceName.substring(ABS_CLASS_RESOURCE_PREFIX.length());
                }
                try (InputStream inputStream = ClassLoaderUtils.getClassLoader().getResourceAsStream(sourceName)) {
                    if (inputStream != null) {
                        doResponse(inputStream, relativeRequestPath, response);
                        return;
                    }
                } catch (IOException e) {
                    // Noting doing
                }
            } else {
                File file;
                try {
                    relativeRequestPath = URLDecoder.decode(relativeRequestPath, StandardCharsets.UTF_8.name());
                    file = new File(directory + relativeRequestPath);
                } catch (UnsupportedEncodingException e) {
                    throw new ResponseNutException(ResponseCode.INTERNAL_SERVER_ERROR.getDescription(), e, ResponseCode.INTERNAL_SERVER_ERROR.getCode());
                }
                if (file.exists() && file.isFile() && file.canRead()) {
                    doResponse(file, response);
                    return;
                }
                if (directoryPreviewStatus && file.exists() && file.isDirectory()) {
                    if (!ABS_CLASS_RESOURCE_PREFIX.equals(path) && !path.endsWith(ABS_CLASS_RESOURCE_PREFIX)) {
                        response.sendRedirect(path + ABS_CLASS_RESOURCE_PREFIX);
                        return;
                    }
                    String staticPath = this.path.getPath();
                    if (!staticPath.endsWith(ABS_CLASS_RESOURCE_PREFIX)) {
                        staticPath += ABS_CLASS_RESOURCE_PREFIX;
                    }
                    doResponseDirectory(file, relativeRequestPath, response, staticPath.equals(path));
                    return;
                }
            }
        }
        throw new ResponseNutException("Resource Not Found.", ResponseCode.NOT_FOUND);
    }

    /**
     * 获取相对请求路径
     *
     * @param request 请求对象
     * @param path    资源路径
     * @return 相对请求路径
     */
    protected String getRelativeRequestPath(Request request, Path path) {
        Path requestPath = new Path(request.getPath());
        String relativePath = requestPath.getPath().substring(requestPath.getPath().indexOf(path.getPath()) + path.getPath().length());
        return StringUtils.isEmpty(relativePath)
                ? ABS_CLASS_RESOURCE_PREFIX : relativePath.startsWith(ABS_CLASS_RESOURCE_PREFIX)
                ? relativePath : ABS_CLASS_RESOURCE_PREFIX + relativePath;
    }

    /**
     * 执行静态资源目录响应
     *
     * @param file     文件
     * @param filePath 文件路径
     * @param response 响应对象
     * @param isRoot   是否是根目录
     */
    protected void doResponseDirectory(File file, String filePath, Response response, boolean isRoot) {
        StringBuilder html = new StringBuilder(PREVIEW_START_HTML1).append(filePath)
                .append(PREVIEW_START_HTML2).append(filePath).append(PREVIEW_START_HTML3);
        if (!isRoot) {
            html.append(PREVIEW_PREVIOUS_HTML).append("\n");
        }
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss", Locale.ENGLISH);
        File[] childFiles = file.listFiles();
        if (childFiles == null) {
            throw new ResponseNutException(ResponseCode.FORBIDDEN);
        }
        for (File childFile : Arrays.stream(childFiles).sorted(Comparator.comparing(File::isDirectory)
                .reversed().thenComparing(File::getName)).collect(Collectors.toList())) {
            String childFileName = childFile.getName();
            int spacing = childFile.isDirectory() ? DEFAULT_SPACING - 1 : DEFAULT_SPACING;
            int length = spacing - getStringLength(childFileName);
            if (length < 0) {
                childFileName = getMaxIgnoreString(childFileName, spacing - 4) + "...";
            }
            if (childFile.isDirectory()) {
                childFileName += ABS_CLASS_RESOURCE_PREFIX;
            }
            html.append("<a href=\"").append(childFile.getName()).append("\">").append(childFileName);
            html.append("</a>").append(String.format("%" + (DEFAULT_SPACING - getStringLength(childFileName)) + "s", ""));
            html.append(simpleDateFormat.format(childFile.lastModified()));
            html.append(String.format("%" + (20 - String.valueOf(childFile.isDirectory()
                    ? 1 : childFile.length()).length()) + "s", ""));
            if (childFile.isDirectory()) {
                html.append("-");
            } else if (childFile.isFile()) {
                html.append(childFile.length());
            }
            html.append("\n");
        }
        response.writeHtml(html.append(PREVIEW_END_HTML).toString());
    }

    /**
     * 执行静态资源响应
     *
     * @param file     文件
     * @param response 响应对象
     */
    protected void doResponse(File file, Response response) {
        try {
            doResponse(Files.newInputStream(file.toPath()), file.getAbsolutePath(), response);
        } catch (IOException e) {
            throw new NutException("Static Resource Read Failure.", e);
        }
    }

    /**
     * 执行静态资源响应
     *
     * @param inputStream         文件输入流
     * @param relativeRequestPath 相对文件地址
     * @param response            响应对象
     */
    protected void doResponse(InputStream inputStream, String relativeRequestPath, Response response) {
        try (InputStream bufferedInputStream = new BufferedInputStream(inputStream, 64 * 1024)) {
            response.write(bufferedInputStream,
                    MediaType.getMediaTypeByPostfix(StringUtils.getFilenameExtension(relativeRequestPath)));
            inputStream.close();
        } catch (Exception e) {
            throw new NutException("Static Resource Read Failure.", e);
        }
    }

    /**
     * 返回包含中文的字符串字符长度
     * @param string 字符串
     * @return 包含中文的字符串字符长度
     */
    private int getStringLength(String string) {
        int length = 0;
        String chinese = "[\u4e00-\u9fa5]";
        for (int i = 0; i < string.length(); i++) {
            String temp = string.substring(i, i + 1);
            if (temp.matches(chinese)) {
                length += 2;
            } else {
                length += isChinesePunctuation(string.charAt(i)) ? 2 : 1;
            }
        }
        return length;
    }

    /**
     * 返回包含忽略符号的字符串
     * @param string 字符串
     * @param maxLength 最大长度
     * @return 包含忽略符号的字符串
     */
    private String getMaxIgnoreString(String string, int maxLength) {
        StringBuilder stringBuilder = new StringBuilder();
        int length = 0;
        String chinese = "[\u4e00-\u9fa5]";
        for (int i = 0; i < string.length(); i++) {
            if (length >= maxLength) {
                break;
            }
            String temp = string.substring(i, i + 1);
            if (temp.matches(chinese)) {
                length += 2;
            } else {
                length += isChinesePunctuation(string.charAt(i)) ? 2 : 1;
            }
            stringBuilder.append(temp);
        }
        return stringBuilder.toString();
    }

    /**
     * 判断是否是中文标点符号
     * @param c 字符
     * @return 是否是中文标点符号
     */
    public boolean isChinesePunctuation(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        return ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS
                || ub == Character.UnicodeBlock.VERTICAL_FORMS;
    }
}